﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using xn;
using xnv;
using KinectCore;
using System.Threading;

namespace KinectCore.hwKinectComponents
{
    /// <summary>
    /// A class that represents a grid of Kinect interaction enabled elements (<code>HWKinectComponent</code>)
    /// </summary>
    public class HWKinectGrid
    {

        private bool shouldRun;
        private Thread KinectReadThread;

        private Context context;
        private SessionManager sessionManager;

        private SelectableSlider1D optionSelector1D;
        private SelectableSlider2D optionSelector2D;

        private FlowRouter flowRouter;
        private Broadcaster broadCaster;

        private SortedDictionary<int, HWKinectComponent<Object>[]> elementMap;

        private int rowCount;
        private int columnCount;

        private Direction selectionDirection;

        //TODO: Add handlers???

        #region Constructors

        /// <summary>
        /// Private Constructor - initialises variables required by both instantiated types of this class
        /// </summary>
        private HWKinectGrid()
        {
            // TODO: ADD CONTEXT LOCATION
            this.context = new Context();

            this.sessionManager = new SessionManager(this.context, "Wave,RaiseHand", "RaiseHand");

            this.flowRouter = new FlowRouter();
            this.broadCaster = new Broadcaster();

            this.elementMap = new SortedDictionary<int, HWKinectComponent<Object>[]>();

            this.shouldRun = true;
            this.KinectReadThread = new Thread(KinectReadThreadMethod);
            this.KinectReadThread.Start();

        }

        /// <summary>
        /// Creates a new 1 Dimensional grid
        /// </summary>
        /// <param name="items">The number of items in the grid</param>
        /// <param name="axis">The axis along which the grid is based</param>
        /// <param name="selectionDirection">The direction of motion used to select an item in the grid</param>
        public HWKinectGrid(int items, Axis axis, Direction selectionDirection)
            : this()
        {
            //TODO: add code to ensure selectedDirection is not along axis 
            this.optionSelector1D = new SelectableSlider1D(items, axis);
            this.optionSelector1D.ItemHover += new SelectableSlider1D.ItemHoverHandler(optionSelector1D_ItemHover);
            this.optionSelector1D.ItemSelect += new SelectableSlider1D.ItemSelectHandler(optionSelector1D_ItemSelect);

            this.rowCount = items;
            this.columnCount = 1;

            // Initialise the grid
            HWKinectComponent<Object>[] itemList = new HWKinectComponent<object>[1];
            this.elementMap.Add(0, itemList);

            this.selectionDirection = selectionDirection;

            this.broadCaster.AddListener(optionSelector1D);
        }

        /// <summary>
        /// Creates a new 2 Dimensional grid
        /// </summary>
        /// <param name="itemsX">The number of elements contained in a row of the grid</param>
        /// <param name="itemsY">The number of elements contained in a column of the grid</param>
        /// <param name="selectionDirection">The direction of motion used to select an item in the grid</param>
        public HWKinectGrid(int itemsX, int itemsY, Direction selectionDirection)
            : this()
        {
            //TODO: add code to ensure selectedDirection IS IN the z axis
            this.optionSelector2D = new SelectableSlider2D(itemsX, itemsY);
            this.optionSelector2D.ItemHover += new SelectableSlider2D.ItemHoverHandler(optionSelector2D_ItemHover);
            this.optionSelector2D.ItemSelect += new SelectableSlider2D.ItemSelectHandler(optionSelector2D_ItemSelect);

            this.rowCount = itemsX;
            this.columnCount = itemsY;

            // Initialise the grid
            for (int i = 0; i < itemsY; i++)
            {
                HWKinectComponent<Object>[] itemList = new HWKinectComponent<object>[itemsX];
                this.elementMap.Add(i, itemList);
            }

            this.selectionDirection = selectionDirection;



            this.broadCaster.AddListener(optionSelector2D);
        }

        #endregion

        #region 1D Option Selector

        /// <summary>
        /// Code called when an element is selected
        /// </summary>
        /// <param name="index">The index of the selected element</param>
        /// <param name="dir">The direction of the motion used to select the element</param>
        void optionSelector1D_ItemSelect(int index, Direction dir)
        {
            //TODO: Add error handling
            if (selectionDirection.Equals(dir))
            {
                if (this.ItemSelected != null)
                {
                    HWKinectComponent<Object>[] activeRow = elementMap[1];
                    HWKinectComponent<Object> selectedItem = activeRow.ElementAt(index);
                    ItemSelected(ref selectedItem);
                }
            }
        }

        /// <summary>
        /// Code called when an element is hovered over
        /// </summary>
        /// <param name="index">The index of the element which is hovered over</param>
        void optionSelector1D_ItemHover(int index)
        {

            //TODO: Add error handling
            if (this.ItemActive != null)
            {
                HWKinectComponent<Object>[] activeRow = elementMap[1];
                HWKinectComponent<Object> activeItem = activeRow.ElementAt(index);
                ItemActive(ref activeItem);
            }
        }

        #endregion

        #region 2D Option Selector

        /// <summary>
        /// Code called when an element is selected
        /// </summary>
        /// <param name="x">The row of the selected element</param>
        /// <param name="y">The column of the selected element</param>
        /// <param name="dir">The direction of the motion used to select the element</param>
        void optionSelector2D_ItemSelect(int x, int y, Direction dir)
        {
            //TODO: Add error handling
            if (selectionDirection.Equals(dir))
            {
                if (this.ItemSelected != null)
                {
                    HWKinectComponent<Object>[] activeRow = elementMap[y];
                    HWKinectComponent<Object> selectedItem = activeRow.ElementAt(x);
                    ItemSelected(ref selectedItem);
                }
            }
        }

        /// <summary>
        /// Code called when an element is hovered over
        /// </summary>
        /// <param name="x">The row of the element which is hovered over</param>
        /// <param name="y">The column of the element which is hovered over</param>
        void optionSelector2D_ItemHover(int x, int y)
        {

            //TODO: Add error handling
            if (this.ItemActive != null)
            {
                HWKinectComponent<Object>[] activeRow = elementMap[y];
                HWKinectComponent<Object> activeItem = activeRow.ElementAt(x);
                ItemActive(ref activeItem);
            }
        }

        #endregion

        #region Populate Grid


        /// <summary>
        /// Adds <code>element</code> to the grid at row 0 column <code>columnID</code>
        /// </summary>
        /// <param name="columnID">The column number to add the row to</param>
        /// <param name="element">The element to add to the grid</param>
        /// <returns><code>True</code> if and only <code>element</code> is added to the grid</returns>
        public bool AddItemToGrid(int columnID, ref HWKinectComponent<Object> element)
        {

            if (element == null || columnID > columnCount)
            {
                return false;
            }

            elementMap[0][columnID] = element;

            return true;

        }

        /// <summary>
        /// Adds <code>element</code> to the grid at row <code>rowID</code> column <code>columnID</code>
        /// </summary>
        /// <param name="columnID">The number of the column to add the element to</param>
        /// <param name="rowID">The number of the row to add the element to</param>
        /// <param name="element">The element to add to the grid</param>
        /// <returns><code>True</code> if and only <code>element</code> is added to the grid</returns>
        public bool AddItemToGrid(int columnID, int rowID, ref HWKinectComponent<Object> element)
        {

            if (element == null || columnID > columnCount || rowID > rowCount)
            {
                return false;
            }

            elementMap[rowID][columnID] = element;

            return true;
        }



        #endregion


        /// <summary>
        /// Runs the thread to update the sensor data from the Kinect
        /// </summary>
        private void KinectReadThreadMethod()
        {

            while (this.shouldRun)
            {
                this.context.WaitAndUpdateAll();
                this.sessionManager.Update(this.context);
            }


        }

        #region Item Active Event
        public delegate void ItemActiveHandler(ref HWKinectComponent<Object> activeItem);
        public event ItemActiveHandler ItemActive;
        #endregion

        #region Item Selected Event
        public delegate void ItemSelectedHandler(ref HWKinectComponent<Object> selectedItem);
        public event ItemSelectedHandler ItemSelected;
        #endregion

    }
}
